/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.smarttv.querygraph;

import com.hp.hpl.jena.graph.Factory;
import com.hp.hpl.jena.graph.Graph;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.sparql.core.TriplePath;
import com.hp.hpl.jena.sparql.syntax.Element;
import com.hp.hpl.jena.sparql.syntax.ElementGroup;
import com.hp.hpl.jena.sparql.syntax.ElementPathBlock;
import com.hp.hpl.jena.sparql.syntax.ElementTriplesBlock;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.RDF;
import edu.yonsei.iwb.smarttv.model.SMARTTVModel;
import edu.yonsei.iwb.smarttv.model.measure.WeightModel;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

/**
 *
 * @author Soo
 */
public class SearchResultGraphGenerator {

    Model model;
    Query query;
    SMARTTVModel tvModel;

    public SearchResultGraphGenerator(SMARTTVModel tvModel, Query query) {
        this.tvModel = tvModel;
        this.model = tvModel.getJenaModel();
        this.query = query;
    }

    public Vector<Graph> generateSearchResultGraph() {
        Vector<Graph> graphSet = new Vector<Graph>();     //create an empty graph 
        Vector<GraphWithScore> graphSetWithScore = new Vector<GraphWithScore>();
        String targetVar = this.query.getResultVars().get(0);

        Query tempQuery = new Query();
        tempQuery.setQuerySelectType();
        tempQuery.setQueryResultStar(true);     //SELECT *
        tempQuery.setQueryPattern(this.query.getQueryPattern());
        List<String> tempVar = tempQuery.getResultVars();   //모든 변수 다 나옴.

        Query newQuery = new Query();
        newQuery.setQuerySelectType();
        newQuery.setQueryPattern(this.query.getQueryPattern());

        //모든 변수에서 기존의 SELECT 문에 있던 변수 제거.
        if (tempVar.contains(targetVar)) {
            tempVar.remove(targetVar);
        }
        if (tempVar.size() == 0) {
            List<Triple> basicGraphPattern = getBasicGraphPattern(newQuery);
            Graph graph = Factory.createDefaultGraph();
            for (int i = 0; i < basicGraphPattern.size(); i++) {
                graph.add(basicGraphPattern.get(i));
            }
            graphSet.add(graph);
            return graphSet;
        }
        //남은 변수를 새로운 쿼리에 add함.
        for (int i = 0; i < tempVar.size(); i++) {
            newQuery.addResultVar(tempVar.get(i));
        }

        QueryExecution qe = QueryExecutionFactory.create(newQuery, this.model);
        ResultSet results = qe.execSelect();
        //쿼리의 WHERE절에 들어있는 모든 트리플.
        List<Triple> basicGraphPattern = getBasicGraphPattern(newQuery);
        //타겟 변수(더미노드 포함)와 연결되어 있는 트리플
        basicGraphPattern = this.getTargetTriples(basicGraphPattern, targetVar);
        while (results.hasNext()) {
            List<Triple> newTriples = new ArrayList();
            Graph graph = Factory.createDefaultGraph();
            QuerySolution qs = results.nextSolution();
            
            for (int i = 0; i < basicGraphPattern.size(); i++) {
                Triple t = basicGraphPattern.get(i);
                Node object = t.getObject();
                Node predicate = t.getPredicate();
                Node subject = t.getSubject();
                //각 노드에서 변수들은 해당 리소스로 대체
                if (subject.isVariable()) {
                    String var = subject.toString().substring(1);
                    if (!var.equals(targetVar)) {
                        subject = qs.getResource(var).asNode();
                    }
                }
                if (object.isVariable()) {
                    String var = object.toString().substring(1);
                    if (!var.equals(targetVar)) {
                        object = qs.get(var).asNode();
                    }
                }
                if (predicate.isVariable()) {
                    String var = predicate.toString().substring(1);
                    predicate = qs.getResource(var).asNode();
                }
                //리소스로 채운 S,P,O로 새로운 트리플 생성
                Triple newTriple = Triple.create(subject, predicate, object);
                if (!newTriples.contains(newTriple)) {
                    newTriples.add(newTriple);
                }
            }
            //타겟 변수 제외한 나머지 변수들의 타입 정보
            Vector<Triple> removeTriples = new Vector<Triple>();
            Node type = RDF.type.asNode();
            for (int m = 0; m < newTriples.size(); m++) {
                Node subject = newTriples.get(m).getSubject();
                Node predicate = newTriples.get(m).getPredicate();
                if ((!subject.isVariable()) && predicate.equals(type)) {
                    removeTriples.add(newTriples.get(m));
                }
            }
            //위에서 구한 나머지 변수들의 타입 정보 제거
            newTriples.removeAll(removeTriples);
            //트리플을 그래프에 채워넣기
            for (int m = 0; m < newTriples.size(); m++) {
                graph.add(newTriples.get(m));
            }
            //그래프의 스코어 가져오기
            double score = getScore(graph);
            if (graphSetWithScore.isEmpty()) {
                graphSetWithScore.add(new GraphWithScore(graph, score));
            } else {
                if (!isContain(graphSetWithScore, graph)) {
                    graphSetWithScore.add(new GraphWithScore(graph, score));
                }
            }
        }
        //작은거로부터 큰거로의 정렬
        graphSet = getOrderedGraphSet(graphSetWithScore);
        qe.close();
        return graphSet;
    }

    private List<Triple> getBasicGraphPattern(Query query) {
        List<Triple> basicGraphPattern = new ArrayList<Triple>();
        ElementGroup whereClause = (ElementGroup) (query.getQueryPattern());
        for (Element e : whereClause.getElements()) {
            if (e instanceof ElementPathBlock) {
                for (TriplePath triplePath : ((ElementPathBlock) e).getPattern()) {
                    if (triplePath.asTriple() == null) {
                        throw new IllegalArgumentException(String.format("this method does not support ARQ TriplePaths, TriplePath: %s", triplePath));
                    }
                    basicGraphPattern.add(triplePath.asTriple());
                }
            }
            if (e instanceof ElementTriplesBlock) {
                for (Iterator<Triple> i = ((ElementTriplesBlock) e).patternElts(); i.hasNext();) {
                    basicGraphPattern.add(i.next());
                }
            }
        }
        return basicGraphPattern;
    }

    private List<Triple> getTargetTriples(List<Triple> newTriples, String originalVars) {
        List<Node> ov = new ArrayList<Node>();
        ov.add(ResourceFactory.createResource("?" + originalVars).asNode());
        List<Triple> resultTriples = new ArrayList<Triple>();
        Property type = RDF.type;
        Property isDummy = model.getProperty("http://iweb.yonsei.ac.kr/ontologies/smarttv/isDummy");
        Literal valueTrue = model.createTypedLiteral("true", "http://www.w3.org/2001/XMLSchema#boolean");

        while (!ov.isEmpty()) {
            Node r = ov.get(0);
            List<Triple> tempTriples = new ArrayList<Triple>();
            for (int i = 0; i < newTriples.size(); i++) {
                Node subject = newTriples.get(i).getSubject();
                Node object = newTriples.get(i).getObject();
                if (subject.toString().equals(r.toString()) | object.toString().equals(r.toString())) {
                    tempTriples.add(newTriples.get(i));
                    if (!resultTriples.contains(newTriples.get(i))) {
                        resultTriples.add(newTriples.get(i));
                    }
                }
            }
            for (int i = 0; i < tempTriples.size(); i++) {
                Node subject = tempTriples.get(i).getSubject();
                Node object = tempTriples.get(i).getObject();
                if (subject.isURI()) {
                    boolean checkDummy = false;
                    NodeIterator subjectType = model.listObjectsOfProperty(model.getResource(subject.getURI()), type);
                    while (subjectType.hasNext()) {
                        if (model.listStatements(subjectType.next().asResource(), isDummy, valueTrue).hasNext()) {
                            checkDummy = true;
                            break;
                        }
                    }
                    if (checkDummy) {
                        if (!ov.contains(subject)) {
                            ov.add(subject);
                        }
                    } else {
                    }
                } 
                if (object.isURI()) {
                    boolean checkDummy = false;
                    NodeIterator objectType = model.listObjectsOfProperty(model.getResource(object.getURI()), type);
                    while (objectType.hasNext()) {
                        if (model.listStatements(objectType.next().asResource(), isDummy, valueTrue).hasNext()) {
                            checkDummy = true;
                            break;
                        }
                    }
                    if (checkDummy) {
                        if (!ov.contains(object)) {
                            ov.add(object);
                        }
                    } else {
                    }
                } 
                if(subject.isVariable()){
                    boolean checkDummy = false;
                    for(int m = 0; m < newTriples.size(); m++){
                        if(newTriples.get(m).getSubject().toString().equals(subject.toString()) && newTriples.get(m).getPredicate().toString().equals(type.toString())){
                            if (model.listStatements(model.createResource(newTriples.get(m).getObject().getURI()), isDummy, valueTrue).hasNext()) {
                                checkDummy = true;
                            }
                            if (checkDummy) {
                                if (!ov.contains(subject)) {
                                    ov.add(subject);
                                }
                            }
                        }
                    }
                } 
                if(object.isVariable()){
                    boolean checkDummy = false;
                    for(int m = 0; m < newTriples.size(); m++){
                        if(newTriples.get(m).getSubject().toString().equals(object.toString()) && newTriples.get(m).getPredicate().toString().equals(type.toString())){
                            if (model.listStatements(model.createResource(newTriples.get(m).getObject().getURI()), isDummy, valueTrue).hasNext()) {
                                checkDummy = true;
                            }
                            if (checkDummy) {
                                if (!ov.contains(object)) {
                                    ov.add(object);
                                }
                            }
                        }
                    }
                }
            }
            ov.remove(0);
        }
        return resultTriples;
    }

//    private double getScore(List<Triple> newTriples) {
    private double getScore(Graph graph) {
        ExtendedIterator<Triple> ex = graph.find(null, null, null);
        double score = 0;
        Vector<Resource> resourceSet = new Vector<Resource>();
        Vector<Resource> propertySet = new Vector<Resource>();
        while (ex.hasNext()) {
            Triple triple = ex.next();
            if (triple.getSubject().isURI()) {
                Resource sr = model.getResource(triple.getSubject().getURI());
                if (!resourceSet.contains(sr)) {
                    resourceSet.add(sr);
                }
            }
            if (triple.getObject().isURI()) {
                Resource or = model.getResource(triple.getObject().getURI());
                if (!resourceSet.contains(or)) {
                    resourceSet.add(or);
                }
            }
            if (triple.getPredicate().isURI()) {
                Resource or = model.getResource(triple.getPredicate().getURI());
                if (!propertySet.contains(or)) {
                    propertySet.add(or);
                }
            }
        }
        for (int i = 0; i < resourceSet.size(); i++) {
            double weight = tvModel.getWeightModel().getInstanceWeight(resourceSet.get(i));
            score += weight;
        }
        for (int i = 0; i < propertySet.size(); i++) {
            double weight = tvModel.getWeightModel().getWeight(propertySet.get(i));
            score += weight;
        }
        return score;
    }

    private Vector<Graph> getOrderedGraphSet(Vector<GraphWithScore> graphSetWithScore) {
        Vector<Graph> graphSet = new Vector<Graph>();
        GraphWithScore[] gws = new GraphWithScore[graphSetWithScore.size()];
        for (int i = 0; i < graphSetWithScore.size(); i++) {
            gws[i] = graphSetWithScore.get(i);
        }
        for (int i = 0; i < graphSetWithScore.size() - 1; i++) {
            for (int j = i + 1; j < graphSetWithScore.size(); j++) {
                if (gws[i].getScore() > gws[j].getScore()) {
                    GraphWithScore temp = gws[i];
                    gws[i] = gws[j];
                    gws[j] = temp;
                }
            }
        }
        for (int i = 0; i < gws.length; i++) {
            graphSet.add(gws[i].getGraph());
        }
        return graphSet;
    }

    private boolean isContain(Vector<GraphWithScore> existedTripleSet, Graph newTripleSet) {
        boolean isContain = false;
        List<List<Triple>> eTriplesSet = new ArrayList<List<Triple>>();
        List<Triple> nTriples = new ArrayList<Triple>();

        for (int i = 0; i < existedTripleSet.size(); i++) {
            List<Triple> eTriples = new ArrayList<Triple>();
            ExtendedIterator<Triple> eEx = existedTripleSet.get(i).getGraph().find(null, null, null);
            while (eEx.hasNext()) {
                Triple triple = eEx.next();
                eTriples.add(triple);
            }
            eTriplesSet.add(eTriples);
        }
        ExtendedIterator<Triple> nEx = newTripleSet.find(null, null, null);
        while (nEx.hasNext()) {
            Triple triple = nEx.next();
            nTriples.add(triple);
        }
        if (eTriplesSet.contains(nTriples)) {
            isContain = true;
        }
        return isContain;
    }

    class GraphWithScore {

        Graph graph;
        double score;

        public GraphWithScore(Graph graph, double score) {
            this.graph = graph;
            this.score = score;
        }

        public Graph getGraph() {
            return this.graph;
        }

        public double getScore() {
            return this.score;
        }
    }
}
